{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Normalizing Flow Example\n",
    "\n",
    "Simple Normalizing Flow (RealNVP-style affine coupling) for MNIST using PyTorch.\n",
    "\n",
    "What it does:\n",
    "- Implements a simple affine coupling layer with alternating binary masks\n",
    "- Stacks multiple coupling layers into a flow\n",
    "- Trains on MNIST by maximizing log-likelihood\n",
    "- Generates and saves sample images during training\n",
    "\n",
    "This implementation is intentionally small and educational rather than highly-optimized.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import numpy as np\n",
    "import torchvision\n",
    "import torchvision.transforms as transforms\n",
    "import matplotlib.pyplot as plt\n",
    "import math\n",
    "import os\n",
    "from typing import List\n",
    "from torchvision import transforms, utils"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Utility Masks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_checkerboard_mask(h: int, w: int, invert: bool = False) -> torch.Tensor:\n",
    "    \"\"\"Create a binary checkerboard mask of shape (1, h, w).\"\"\"\n",
    "    mask = [[((i + j) % 2) for j in range(w)] for i in range(h)]\n",
    "    # to numpy\n",
    "    mask = torch.from_numpy(np.array(mask)).float()\n",
    "    if invert:\n",
    "        mask = 1.0 - mask\n",
    "    return mask.unsqueeze(0)  # (1, h, w)\n",
    "\n",
    "# For a flattened image we'll produce a mask vector of length 784\n",
    "def mask_flat_from_checkerboard(h=28, w=28, invert=False) -> torch.Tensor:\n",
    "    return create_checkerboard_mask(h, w, invert).view(-1)  # (784,)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Coupling Layer\n",
    "\n",
    "Affine coupling layer as in RealNVP."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AffineCoupling(nn.Module):\n",
    "    \"\"\"Simple MLP-based affine coupling layer (RealNVP style) for flattened images.\n",
    "\n",
    "    Splits input by mask: x = x_a * mask + x_b * (1-mask)\n",
    "    Transforms the (1-mask) part conditioned on the mask part.\n",
    "    \"\"\"\n",
    "    def __init__(self, dim: int, hidden: int, mask: torch.Tensor):\n",
    "        super().__init__()\n",
    "        self.dim = dim\n",
    "        self.register_buffer(\"mask\", mask)  # (dim,)\n",
    "\n",
    "        # NN outputs scale and shift for the transformed subset\n",
    "        self.net = nn.Sequential(\n",
    "            nn.Linear(dim, hidden),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(hidden, hidden),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(hidden, dim * 2),  # s and t for full dim but we'll mask\n",
    "        )\n",
    "        \n",
    "        # initialize last layer weights near zero for stability at start\n",
    "        #nn.init.normal_(self.net[-1].weight, mean=0.0, std=1e-5)\n",
    "        #nn.init.zeros_(self.net[-1].bias)\n",
    "\n",
    "    def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:\n",
    "        # x shape: (B, dim)\n",
    "        # reshape mask for cnn\n",
    "        masked_x = x * self.mask\n",
    "        params = self.net(masked_x)\n",
    "        s, t = params.chunk(2, dim=1)\n",
    "        # apply mask to only transform the complementary part\n",
    "        s = s * (1 - self.mask)\n",
    "        t = t * (1 - self.mask)\n",
    "        # scale activation: clipped for numerical stability\n",
    "        s = torch.tanh(s) * 2.0  # scale range roughly (-2, 2)\n",
    "\n",
    "        # flatten x\n",
    "        x = x.view(x.size(0), -1)\n",
    "        y = x * torch.exp(s) + t\n",
    "        # keep masked part unchanged explicitly\n",
    "        y = y * (1 - self.mask) + masked_x\n",
    "\n",
    "        # log-determinant is sum of s over transformed dimensions\n",
    "        log_det = s.sum(dim=1)\n",
    "        return y, log_det\n",
    "\n",
    "    def inverse(self, y: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:\n",
    "        masked_y = y * self.mask\n",
    "        params = self.net(masked_y)\n",
    "        s, t = params.chunk(2, dim=1)\n",
    "        s = s * (1 - self.mask)\n",
    "        t = t * (1 - self.mask)\n",
    "        s = torch.tanh(s) * 2.0\n",
    "        y = y.view(y.size(0), -1)\n",
    "        x = (y - t) * torch.exp(-s)\n",
    "        x = x * (1 - self.mask) + masked_y\n",
    "        log_det = -s.sum(dim=1)\n",
    "        return x, log_det"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Architecture\n",
    "\n",
    "The Normalizing Flow model consists of multiple affine coupling layers, each with its own binary mask. The masks alternate between layers to ensure that all dimensions are transformed over the course of the flow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NormalizingFlow(nn.Module):\n",
    "    def __init__(self, dim: int, hidden: int, n_layers: int):\n",
    "        super().__init__()\n",
    "        self.dim = dim\n",
    "        self.layers: List[AffineCoupling] = nn.ModuleList()\n",
    "        \n",
    "        # Create alternating checkerboard masks\n",
    "        for i in range(n_layers):\n",
    "            invert = (i % 2 == 1)\n",
    "            mask = mask_flat_from_checkerboard(28, 28, invert=invert)\n",
    "            layer = AffineCoupling(dim=dim, hidden=hidden, mask=mask)\n",
    "            self.layers.append(layer)\n",
    "\n",
    "    def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:\n",
    "        \"\"\"Transform data to latent space: x -> z\"\"\"\n",
    "        log_det_sum = torch.zeros(x.size(0), device=x.device)\n",
    "        z = x\n",
    "        for layer in self.layers:\n",
    "            z, log_det = layer(z)\n",
    "            log_det_sum = log_det_sum + log_det\n",
    "        return z, log_det_sum\n",
    "\n",
    "    def inverse(self, z: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:\n",
    "        \"\"\"Transform latent to data space: z -> x\"\"\"\n",
    "        log_det_sum = torch.zeros(z.size(0), device=z.device)\n",
    "        x = z\n",
    "        for layer in reversed(self.layers):\n",
    "            x, log_det = layer.inverse(x)\n",
    "            log_det_sum = log_det_sum + log_det\n",
    "        return x, log_det_sum\n",
    "\n",
    "    def log_prob(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        \"\"\"Compute log p(x) = log p(z) + log|det J|\"\"\"\n",
    "        z, log_det = self.forward(x)\n",
    "        # Standard normal log probability\n",
    "        log_pz = -0.5 * (z.pow(2).sum(dim=1) + self.dim * math.log(2 * math.pi))\n",
    "        return log_pz, log_det\n",
    "\n",
    "    def sample(self, num_samples: int, device: torch.device) -> torch.Tensor:\n",
    "        \"\"\"Sample from the model by inverting noise\"\"\"\n",
    "        with torch.no_grad():\n",
    "            z = torch.randn(num_samples, self.dim, device=device)\n",
    "            x, _ = self.inverse(z)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Training\n",
    "\n",
    "The Normalizing Flow model is trained by maximizing the log-likelihood of the training data. The training loop iterates over the dataset, computes the negative log-likelihood loss, and updates the model parameters using the Adam optimizer. The model's performance is monitored by generating and saving sample images at regular intervals during training."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NormalizingFlow(\n",
      "  (layers): ModuleList(\n",
      "    (0-5): 6 x AffineCoupling(\n",
      "      (net): Sequential(\n",
      "        (0): Linear(in_features=784, out_features=1024, bias=True)\n",
      "        (1): ReLU()\n",
      "        (2): Linear(in_features=1024, out_features=1024, bias=True)\n",
      "        (3): ReLU()\n",
      "        (4): Linear(in_features=1024, out_features=1568, bias=True)\n",
      "      )\n",
      "    )\n",
      "  )\n",
      ")\n",
      "Number of model parameters: 20.76 million\n",
      "Running on: cuda\n",
      "Epoch 0, avg loss: -1758.0443, lr: 0.001000\n",
      "    avg log p(x): -69193.3233, avg log|det J|: 181635.7212\n",
      "Epoch 1, avg loss: -2252.3049, lr: 0.000997\n",
      "    avg log p(x): -62040.4612, avg log|det J|: 206112.6612\n",
      "Epoch 2, avg loss: -2342.8353, lr: 0.000989\n",
      "    avg log p(x): -61065.6757, avg log|det J|: 210926.2078\n",
      "Epoch 3, avg loss: -2426.5846, lr: 0.000976\n",
      "    avg log p(x): -59728.5269, avg log|det J|: 214943.8271\n",
      "Epoch 4, avg loss: -2469.0388, lr: 0.000957\n",
      "    avg log p(x): -59615.5538, avg log|det J|: 217545.5448\n",
      "Epoch 5, avg loss: -2520.3510, lr: 0.000933\n",
      "    avg log p(x): -58849.6128, avg log|det J|: 220063.5318\n",
      "Epoch 6, avg loss: -2561.6806, lr: 0.000905\n",
      "    avg log p(x): -58361.2324, avg log|det J|: 222225.5052\n",
      "Epoch 7, avg loss: -2569.6693, lr: 0.000872\n",
      "    avg log p(x): -58605.3320, avg log|det J|: 222979.0612\n",
      "Epoch 8, avg loss: -2587.6199, lr: 0.000835\n",
      "    avg log p(x): -58425.9160, avg log|det J|: 223946.9574\n",
      "Epoch 9, avg loss: -2606.1493, lr: 0.000794\n",
      "    avg log p(x): -58387.1808, avg log|det J|: 225090.4176\n",
      "Epoch 10, avg loss: -2634.0124, lr: 0.000750\n",
      "    avg log p(x): -57754.5414, avg log|det J|: 226244.9204\n",
      "Epoch 11, avg loss: -2648.8740, lr: 0.000703\n",
      "    avg log p(x): -57983.3987, avg log|det J|: 227420.1598\n",
      "Epoch 12, avg loss: -2692.7883, lr: 0.000655\n",
      "    avg log p(x): -57026.7890, avg log|det J|: 229278.4759\n",
      "Epoch 13, avg loss: -2714.8148, lr: 0.000604\n",
      "    avg log p(x): -56819.5461, avg log|det J|: 230477.7893\n",
      "Epoch 14, avg loss: -2682.4791, lr: 0.000552\n",
      "    avg log p(x): -57585.4823, avg log|det J|: 229172.1099\n",
      "Epoch 15, avg loss: -2741.9369, lr: 0.000500\n",
      "    avg log p(x): -56611.0020, avg log|det J|: 231998.5066\n",
      "Epoch 16, avg loss: -2782.7788, lr: 0.000448\n",
      "    avg log p(x): -56031.1083, avg log|det J|: 234034.2639\n",
      "Epoch 17, avg loss: -2810.9501, lr: 0.000396\n",
      "    avg log p(x): -55622.4724, avg log|det J|: 235430.0689\n",
      "Epoch 18, avg loss: -2796.2584, lr: 0.000345\n",
      "    avg log p(x): -56247.1783, avg log|det J|: 235111.6588\n",
      "Epoch 19, avg loss: -2838.4099, lr: 0.000297\n",
      "    avg log p(x): -55346.5939, avg log|det J|: 236906.7646\n",
      "Epoch 20, avg loss: -2856.1746, lr: 0.000250\n",
      "    avg log p(x): -55274.7357, avg log|det J|: 237975.6584\n",
      "Epoch 21, avg loss: -2890.7721, lr: 0.000206\n",
      "    avg log p(x): -54683.0549, avg log|det J|: 239593.3063\n",
      "Epoch 22, avg loss: -2903.6890, lr: 0.000165\n",
      "    avg log p(x): -54522.2934, avg log|det J|: 240258.6803\n",
      "Epoch 23, avg loss: -2919.2267, lr: 0.000128\n",
      "    avg log p(x): -54352.0852, avg log|det J|: 241082.5823\n",
      "Epoch 24, avg loss: -2932.1990, lr: 0.000095\n",
      "    avg log p(x): -54200.9946, avg log|det J|: 241760.7218\n",
      "Epoch 25, avg loss: -2946.6619, lr: 0.000067\n",
      "    avg log p(x): -53944.7820, avg log|det J|: 242432.6454\n",
      "Epoch 26, avg loss: -2954.9407, lr: 0.000043\n",
      "    avg log p(x): -53845.9117, avg log|det J|: 242863.9174\n",
      "Epoch 27, avg loss: -2960.1386, lr: 0.000024\n",
      "    avg log p(x): -53818.8633, avg log|det J|: 243166.4714\n",
      "Epoch 28, avg loss: -2964.7042, lr: 0.000011\n",
      "    avg log p(x): -53756.7941, avg log|det J|: 243395.4653\n",
      "Epoch 29, avg loss: -2966.6478, lr: 0.000003\n",
      "    avg log p(x): -53740.7012, avg log|det J|: 243505.3538\n"
     ]
    }
   ],
   "source": [
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "num_epochs = 30\n",
    "batch_size = 64\n",
    "lr = 1e-3\n",
    "dim = 28 * 28\n",
    "n_layers = 6\n",
    "hidden = 1024\n",
    "model = NormalizingFlow(dim=dim, hidden=hidden, n_layers=n_layers).to(device)\n",
    "print(model)\n",
    "print(f\"Number of model parameters: {sum(p.numel() for p in model.parameters())/1e6:.2f} million\")\n",
    "print(f\"Running on: {device}\")\n",
    "\n",
    "transform = transforms.Compose([\n",
    "    transforms.ToTensor(),  # maps to [0,1]\n",
    "    # transforms.Lambda(lambda t: (t - 0.5) * 2),  # scale to [-1,1]\n",
    "    transforms.Lambda(lambda t: t.view(-1)),  # flatten to 784\n",
    "])\n",
    "# Load the MNIST train dataset\n",
    "train_dataset = torchvision.datasets.MNIST(root='~/data', \n",
    "                                           train=True, \n",
    "                                           download=True, \n",
    "                                           transform=transform)\n",
    "\n",
    "# Create the train dataloader\n",
    "train_loader = torch.utils.data.DataLoader(train_dataset, \n",
    "                                           batch_size=batch_size, \n",
    "                                           shuffle=True)\n",
    "\n",
    "\n",
    "# Define the optimizer and the scheduler\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=lr)\n",
    "scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_epochs)\n",
    "\n",
    "# Set the model to training mode\n",
    "model.train()\n",
    "# Train the model for the specified number of epochs\n",
    "for epoch in range(num_epochs):\n",
    "    total_loss = 0.0\n",
    "    total_log_px = 0.0\n",
    "    total_log_det = 0.0\n",
    "    for batch_idx, (x, _) in enumerate(train_loader):\n",
    "        x = x.to(device)\n",
    "        optimizer.zero_grad()\n",
    "        log_px, log_det = model.log_prob(x)\n",
    "        # we maximize log-likelihood -> minimize negative log-likelihood\n",
    "        loss = -log_px.mean() - log_det.mean()\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "\n",
    "        total_loss += loss.item()\n",
    "        total_log_px += log_px.sum().item()\n",
    "        total_log_det += log_det.sum().item()\n",
    "    \n",
    "    lr = scheduler.get_last_lr()[0]\n",
    "    scheduler.step() \n",
    "    print(f\"Epoch {epoch}, avg loss: {total_loss / (batch_idx + 1):.4f}, lr: {lr:.6f}\")\n",
    "    print(f\"    avg log p(x): {total_log_px / (batch_idx + 1):.4f}, avg log|det J|: {total_log_det / (batch_idx + 1):.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Testing\n",
    "\n",
    "The trained Normalizing Flow model can be evaluated by generating new samples from the learned distribution. This is done by sampling from a standard normal distribution and passing the samples through the inverse of the flow to obtain images in the original data space.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 200x200 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "model.eval()\n",
    "with torch.no_grad():\n",
    "    samples = model.sample(16, device=device)  # (16, dim)\n",
    "    imgs = samples.view(-1, 1, 28, 28)\n",
    "    imgs = torch.tanh(imgs)\n",
    "    # imgs = (imgs + 1) / 2.0\n",
    "    # Clamp to valid range [0, 1] for MNIST\n",
    "    imgs = torch.clamp(imgs, 0.0, 1.0)\n",
    "    grid = utils.make_grid(imgs, nrow=4, padding=0, normalize=False)\n",
    "    # visualize the samples inline (if using Jupyter)\n",
    "    plt.figure(figsize=(2, 2))\n",
    "    plt.axis('off')\n",
    "    plt.imshow(grid.permute(1, 2, 0).cpu().numpy(), cmap='gray')\n",
    "    plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "mspeech",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
